 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.update.internal.ui.wizards;

 import java.lang.reflect.InvocationTargetException ;
 import java.net.MalformedURLException ;
 import java.net.URL ;
 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.Collection ;
 import java.util.HashSet ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.Set ;

 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.jface.dialogs.Dialog;
 import org.eclipse.jface.dialogs.DialogPage;
 import org.eclipse.jface.dialogs.ErrorDialog;
 import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.jface.dialogs.IMessageProvider;
 import org.eclipse.jface.operation.IRunnableWithProgress;
 import org.eclipse.jface.viewers.CheckStateChangedEvent;
 import org.eclipse.jface.viewers.ICheckStateListener;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.ISelectionProvider;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.ITreeContentProvider;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.jface.viewers.ViewerFilter;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.BusyIndicator;
 import org.eclipse.swt.custom.SashForm;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.TreeItem;
 import org.eclipse.ui.PartInitException;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.dialogs.PropertyDialogAction;
 import org.eclipse.ui.forms.HyperlinkSettings;
 import org.eclipse.ui.forms.events.HyperlinkAdapter;
 import org.eclipse.ui.forms.events.HyperlinkEvent;
 import org.eclipse.ui.forms.widgets.ScrolledFormText;
 import org.eclipse.update.core.ICategory;
 import org.eclipse.update.core.IFeature;
 import org.eclipse.update.core.IFeatureReference;
 import org.eclipse.update.core.IImport;
 import org.eclipse.update.core.IIncludedFeatureReference;
 import org.eclipse.update.core.IPluginEntry;
 import org.eclipse.update.core.ISiteFeatureReference;
 import org.eclipse.update.core.IURLEntry;
 import org.eclipse.update.core.Utilities;
 import org.eclipse.update.core.VersionedIdentifier;
 import org.eclipse.update.internal.core.ExtendedSite;
 import org.eclipse.update.internal.core.UpdateCore;
 import org.eclipse.update.internal.core.UpdateManagerUtils;
 import org.eclipse.update.internal.operations.FeatureStatus;
 import org.eclipse.update.internal.operations.OperationValidator;
 import org.eclipse.update.internal.operations.UpdateUtils;
 import org.eclipse.update.internal.operations.OperationValidator.InternalImport;
 import org.eclipse.update.internal.operations.OperationValidator.RequiredFeaturesResult;
 import org.eclipse.update.internal.ui.UpdateUI;
 import org.eclipse.update.internal.ui.UpdateUIImages;
 import org.eclipse.update.internal.ui.UpdateUIMessages;
 import org.eclipse.update.internal.ui.model.FeatureReferenceAdapter;
 import org.eclipse.update.internal.ui.model.SimpleFeatureAdapter;
 import org.eclipse.update.internal.ui.model.SiteBookmark;
 import org.eclipse.update.internal.ui.model.SiteCategory;
 import org.eclipse.update.internal.ui.parts.DefaultContentProvider;
 import org.eclipse.update.internal.ui.parts.SWTUtil;
 import org.eclipse.update.internal.ui.parts.SharedLabelProvider;
 import org.eclipse.update.operations.IInstallFeatureOperation;
 import org.eclipse.update.operations.IUpdateModelChangedListener;
 import org.eclipse.update.operations.OperationsManager;
 import org.eclipse.update.search.IUpdateSearchSite;
 import org.eclipse.update.search.UpdateSearchRequest;

 public class ReviewPage extends BannerPage {

     private Label label;
     private ArrayList jobs;
     private Label counterLabel;
     private IStatus validationStatus;
     private Collection problematicFeatures = new HashSet ();
     // feature that was recently selected or null
 private IFeature newlySelectedFeature;
     //
 private FeatureStatus lastDisplayedStatus;
     private PropertyDialogAction propertiesAction;
     private ScrolledFormText descLabel;
     private Button statusButton;
     private Button moreInfoButton;
     private Button propertiesButton;
     private Button selectRequiredFeaturesButton;
     private Button filterCheck;
     private Button filterOlderVersionCheck;
     private ContainmentFilter filter = new ContainmentFilter();
     private LatestVersionFilter olderVersionFilter = new LatestVersionFilter();
     private UpdateSearchRequest searchRequest;
     //private int LABEL_ORDER = 1;
 //private int VERSION_ORDER = 1;
 //private int PROVIDER_ORDER = 1;
 private ContainerCheckedTreeViewer treeViewer;
     private boolean initialized;
     private boolean isUpdateSearch;
   
     
     class TreeContentProvider extends DefaultContentProvider implements
             ITreeContentProvider {

         public Object [] getElements(Object parent) {
             return getSites();
         }

         public Object [] getChildren(final Object parent) {
             if (parent instanceof SiteBookmark) {
                 SiteBookmark bookmark = (SiteBookmark) parent;
                 bookmark.getSite(null); // triggers catalog creation
 Object [] children = bookmark.getCatalog(true,null);
                 ArrayList nonEmptyCategories = new ArrayList (children.length);
                 for (int i=0; i<children.length; i++)
                     if (hasChildren(children[i]))
                         nonEmptyCategories.add(children[i]);
                 return nonEmptyCategories.toArray();
             } else if (parent instanceof SiteCategory) {
                 SiteCategory category = (SiteCategory)parent;
                 //return category.getChildren();
 Object [] children = category.getChildren();
                 ArrayList list = new ArrayList (children.length);
                 for (int i=0; i<children.length; i++) {
                     if (children[i] instanceof FeatureReferenceAdapter) {
                         try {
                             IInstallFeatureOperation job = findJob((FeatureReferenceAdapter)children[i]);
                             if (job != null)
                                 list.add(job);
                         } catch (CoreException e) {
                             UpdateCore.log(e.getStatus());
                         }
                     }
                 }
                 return list.toArray();
             }
             return new Object [0];
         }

         public Object getParent(Object element) {
             if (element instanceof SiteCategory)
                 return ((SiteCategory) element).getBookmark();
             if (element instanceof IInstallFeatureOperation) {
                 IFeature f = ((IInstallFeatureOperation)element).getFeature();
                 ISiteFeatureReference fr = f.getSite().getFeatureReference(f);
                 ICategory[] categories = fr.getCategories();
 // if (categories != null && categories.length > 0)
 // return categories[0];
 SiteBookmark[] sites = (SiteBookmark[])((ITreeContentProvider)treeViewer.getContentProvider()).getElements(null);
                 for (int i=0; i<sites.length; i++) {
                     try {
                         if (sites[i].getSite(false, null).getURL() != f.getSite().getSiteContentProvider().getURL()) {
                             // if the site has mirrors check if this is from the mirror that user selected
 if (sites[i].getSite(false, null) instanceof ExtendedSite) {
                                 ExtendedSite site = (ExtendedSite)sites[i].getSite(false, null);
                                 IURLEntry siteMirror = site.getSelectedMirror();
                                 if (siteMirror != null && siteMirror.getURL().toExternalForm().equals(f.getSite().getSiteContentProvider().getURL().toExternalForm())) {
                                     // this is the site so proceed with the loop
 } else {
                                     continue;
                                 }
                             } else {
                                 continue;
                             }
                         }
                     } catch (CoreException ce) {
                         return null;
                     }
                     Object [] children = sites[i].getCatalog(true, null);
                     for (int j = 0; j<children.length; j++) {
                         if (!(children[j] instanceof SiteCategory))
                             continue;
                         for (int c=0; c < categories.length; c++)
                             if (categories[c].getName().equals(((SiteCategory)children[j]).getName()))
                                 return children[j];
                     }
                 }
             }

             return null;
         }

         public boolean hasChildren(Object element) {
             return (element instanceof SiteBookmark || (
                     element instanceof SiteCategory && getChildren(element).length > 0));
         }

         private SiteBookmark[] getSites() {
             if (searchRequest == null)
                 return new SiteBookmark[0];
             else if (searchRequest.getScope().getSearchSites() == null ||
                 searchRequest.getScope().getSearchSites().length == 0) {
                 // this is an update search, so see if there are any jobs first,
 // and get their sites
 if (jobs != null) {
                     ArrayList sitesList = new ArrayList (jobs.size());
                     for (int i = 0; i < jobs.size(); i++) {
                         IInstallFeatureOperation op = (IInstallFeatureOperation) jobs
                                 .get(i);
                         // we need a label for the site, so try to get it from the old
 // feature update url
 String label = null;
                         IFeature[] existingFeatures = UpdateUtils
                                 .getInstalledFeatures(op.getFeature(), true);
                         if (existingFeatures != null
                                 && existingFeatures.length > 0) {
                             IURLEntry entry = op.getFeature()
                                     .getUpdateSiteEntry();
                             label = entry.getAnnotation();
                         }
                         if (label == null)
                             label = op.getFeature().getSite().getURL().toExternalForm();
                                     
                         SiteBookmark bookmark = new SiteBookmark(label,
                                 op.getFeature().getSite().getURL(), false);
                         if (sitesList.contains(bookmark))
                             continue;
                         else
                             sitesList.add(bookmark);
                         
                     }
                     if (!sitesList.isEmpty())
                         return (SiteBookmark[]) sitesList
                                 .toArray(new SiteBookmark[sitesList.size()]);
                 }
                 return new SiteBookmark[0];
             } else {
                 // search for features
 IUpdateSearchSite[] sites = searchRequest.getScope().getSearchSites();
                 SiteBookmark[] siteBookmarks = new SiteBookmark[sites.length];
                 for (int i = 0; i < sites.length; i++)
                     siteBookmarks[i] = new SiteBookmark(sites[i].getLabel(),
                             sites[i].getURL(), false);
                 return siteBookmarks;
             }
         }
     }

     class TreeLabelProvider extends SharedLabelProvider {

         public Image getImage(Object obj) {
             if (obj instanceof SiteBookmark)
                 return UpdateUI.getDefault().getLabelProvider().get(
                         UpdateUIImages.DESC_SITE_OBJ);
             if (obj instanceof SiteCategory)
                 return UpdateUI.getDefault().getLabelProvider().get(
                         UpdateUIImages.DESC_CATEGORY_OBJ);
             if (obj instanceof IInstallFeatureOperation) {
                 IFeature feature = ((IInstallFeatureOperation) obj).getFeature();
                 boolean patch = feature.isPatch();
                 
                 //boolean problematic=problematicFeatures.contains(feature) && treeViewer.getChecked(obj);
 boolean featureIsProblematic = isFeatureProblematic(feature);
                 boolean problematic = treeViewer.getChecked(obj) && featureIsProblematic;
                 
                 if (!problematic && featureIsProblematic) {
                     Object parent = ((TreeContentProvider)treeViewer.getContentProvider()).getParent(obj);
                     problematic = treeViewer.getChecked(parent) && !treeViewer.getGrayed(parent);
                 }
                 
                 if (patch) {
                     return get(UpdateUIImages.DESC_EFIX_OBJ, problematic? F_ERROR : 0);
                 } else {
                     return get(UpdateUIImages.DESC_FEATURE_OBJ, problematic? F_ERROR : 0);
                 }
             }
             return super.getImage(obj);

         }

         public String getText(Object obj) {
             if (obj instanceof SiteBookmark)
                 return ((SiteBookmark) obj).getLabel();
             /*
             if (obj instanceof SiteCategory)
                 return obj.toString();
                 */
             if (obj instanceof IInstallFeatureOperation) {
                 IInstallFeatureOperation job = (IInstallFeatureOperation) obj;
                 IFeature feature = job.getFeature();
                 return feature.getLabel() + " " + feature //$NON-NLS-1$
 .getVersionedIdentifier()
                             .getVersion()
                             .toString();
             }
             return super.getText(obj);
         }
     }

     class ModelListener implements IUpdateModelChangedListener {
         public void objectChanged(Object object, String property) {
             treeViewer.refresh();
             checkItems();
         }

         public void objectsAdded(Object parent, Object [] children) {
             treeViewer.refresh();
             checkItems();
         }

         public void objectsRemoved(Object parent, Object [] children) {
             treeViewer.refresh();
             checkItems();
         }
         
         private void checkItems() {
             TreeItem[] items = treeViewer.getTree().getItems();
             for (int i = 0; i < items.length; i++) {
                 SiteBookmark bookmark = (SiteBookmark) items[i].getData();
                 treeViewer.setChecked(bookmark, bookmark.isSelected());
                 String [] ignoredCats = bookmark.getIgnoredCategories();
                 treeViewer.setGrayed(bookmark, ignoredCats.length > 0
                         && bookmark.isSelected());
             }
         }
     }

     class ContainmentFilter extends ViewerFilter {
         
         private IInstallFeatureOperation[] selectedJobs;
         
         public boolean select(Viewer viewer, Object parent, Object element) {
             if (element instanceof IInstallFeatureOperation) {
                 return !isContained((IInstallFeatureOperation) element) || isSelected( selectedJobs, (IInstallFeatureOperation)element);
             } else if ( (element instanceof SiteCategory) || (element instanceof SiteBookmark)){
                 Object [] children = ((ITreeContentProvider)((ContainerCheckedTreeViewer)viewer).getContentProvider()).getChildren(element);
                 for ( int i = 0; i < children.length; i++) {
                     if (select(viewer, element, children[i])) {
                         return true;
                     }
                 }
                 return false;
             }
         
             return true;
         }
         
         private boolean isContained(IInstallFeatureOperation job) {
             VersionedIdentifier vid = job.getFeature().getVersionedIdentifier();

             for (int i = 0; i < jobs.size(); i++) {
                 IInstallFeatureOperation candidate = (IInstallFeatureOperation) jobs.get(i);
                 if (candidate.equals(job))
                     continue;
                 IFeature feature = candidate.getFeature();
                 if (includes(feature, vid,null))
                     return true;
             }
             return false;
         }
         private boolean includes(IFeature feature, VersionedIdentifier vid, ArrayList cycleCandidates) {
             try {
                 if (cycleCandidates == null)
                     cycleCandidates = new ArrayList ();
                 if (cycleCandidates.contains(feature))
                     throw Utilities.newCoreException(NLS.bind(UpdateUIMessages.InstallWizard_ReviewPage_cycle, feature.getVersionedIdentifier().toString()), null);
                 else
                     cycleCandidates.add(feature);
                 IFeatureReference[] irefs =
                     feature.getIncludedFeatureReferences();
                 for (int i = 0; i < irefs.length; i++) {
                     IFeatureReference iref = irefs[i];
                     IFeature ifeature = UpdateUtils.getIncludedFeature(feature, iref);
                     VersionedIdentifier ivid =
                         ifeature.getVersionedIdentifier();
                     if (ivid.equals(vid))
                         return true;
                     if (includes(ifeature, vid, cycleCandidates))
                         return true;
                 }
                 return false;
             } catch (CoreException e) {
                 return false;
             } finally {
                 // after this feature has been DFS-ed, it is no longer a cycle candidate
 cycleCandidates.remove(feature);
             }
         }


         public IInstallFeatureOperation[] getSelectedJobs() {
             return selectedJobs;
         }
         

         public void setSelectedJobs(IInstallFeatureOperation[] selectedJobs) {
             this.selectedJobs = selectedJobs;
         }

     
     }

     class LatestVersionFilter extends ViewerFilter {
         
         private IInstallFeatureOperation[] selectedJobs;
         
         public boolean select(Viewer viewer, Object parent, Object element) {
                         
             if (element instanceof IInstallFeatureOperation) {
                 return isLatestVersion((IInstallFeatureOperation) element) || isSelected( selectedJobs, (IInstallFeatureOperation)element);
             } else if ( (element instanceof SiteCategory) || (element instanceof SiteBookmark)){
                 Object [] children = ((ITreeContentProvider)((ContainerCheckedTreeViewer)viewer).getContentProvider()).getChildren(element);
                 for ( int i = 0; i < children.length; i++) {
                     if (select(viewer, element, children[i])) {
                         return true;
                     }
                 }
                 return false;
             }
             
             return true;
         }
         
         private boolean isLatestVersion(IInstallFeatureOperation job) {
             IFeature feature = job.getFeature();
             for (int i = 0; i < jobs.size(); i++) {
                 IInstallFeatureOperation candidateJob = (IInstallFeatureOperation) jobs.get(i);
                 if (candidateJob.equals(job))
                     continue;
                 IFeature candidate = candidateJob.getFeature();
                 if (feature.getSite() != job.getFeature().getSite())
                     continue;
                 if (!feature.getVersionedIdentifier().getIdentifier().equals(candidate.getVersionedIdentifier().getIdentifier()))
                     continue;
                 if (!feature.getVersionedIdentifier().getVersion().isGreaterOrEqualTo(candidate.getVersionedIdentifier().getVersion()))
                     return false;
             }
             return true;
         }
         
         public IInstallFeatureOperation[] getSelectedJobs() {
             return selectedJobs;
         }
         
         public void setSelectedJobs(IInstallFeatureOperation[] selectedJobs) {
             this.selectedJobs = selectedJobs;
         }
         
         
     }
     
     class FeaturePropertyDialogAction extends PropertyDialogAction {
         private IStructuredSelection selection;

         public FeaturePropertyDialogAction(
             Shell shell,
             ISelectionProvider provider) {
             super(shell, provider);
         }

         public IStructuredSelection getStructuredSelection() {
             return selection;
         }

         public void selectionChanged(IStructuredSelection selection) {
             this.selection = selection;
         }

     }
     /**
      * Constructor for ReviewPage2
      */
     public ReviewPage(boolean isUpdateSearch, UpdateSearchRequest searchRequest, ArrayList jobs) {
         super("Review"); //$NON-NLS-1$
 this.isUpdateSearch = isUpdateSearch;
         this.jobs = jobs;
         if (this.jobs==null) this.jobs = new ArrayList ();
         this.searchRequest = searchRequest;
         
         setTitle(UpdateUIMessages.InstallWizard_ReviewPage_title);
         setDescription(UpdateUIMessages.InstallWizard_ReviewPage_desc);
         UpdateUI.getDefault().getLabelProvider().connect(this);
         setBannerVisible(false);
     }

     public void dispose() {
         UpdateUI.getDefault().getLabelProvider().disconnect(this);
         super.dispose();
     }

     public void setVisible(boolean visible) {
         super.setVisible(visible);
         
         // when searching for updates, only nested patches can be shown.
 // when searching for features, features and patches can be shown
 String filterText = filterCheck.getText();
         String filterFeatures = UpdateUIMessages.InstallWizard_ReviewPage_filterFeatures;
         String filterPatches = UpdateUIMessages.InstallWizard_ReviewPage_filterPatches;

         if (isUpdateSearch && filterText.equals(filterFeatures))
             filterCheck.setText(filterPatches);
         else if ( !isUpdateSearch && filterText.equals(filterPatches))
             filterCheck.setText(filterFeatures);
         
         if (visible && !initialized) {
             initialized = true;
 // jobs.clear();

 // setDescription(UpdateUI.getString("InstallWizard.ReviewPage.searching")); //$NON-NLS-1$;
 // label.setText(UpdateUI.getString("")); //$NON-NLS-1$

             getShell().getDisplay().asyncExec(new Runnable () {
                 public void run() {
 // searchRunner.runSearch();
 performPostSearchProcessing();
                 }
             });
         }
     }

     private void performPostSearchProcessing() {
         BusyIndicator.showWhile(getShell().getDisplay(), new Runnable () {
             public void run() {
                 if (treeViewer != null) {
 // treeViewer.refresh();
 // treeViewer.getTree().layout(true);
 if (isUpdateSearch) {
                         selectTrueUpdates();
                     }
                 }
                 pageChanged();
                 
                 int totalCount = jobs != null ? jobs.size(): 0;
                 if(totalCount >0) {
                     setDescription(UpdateUIMessages.InstallWizard_ReviewPage_desc);
                     label.setText(UpdateUIMessages.InstallWizard_ReviewPage_label);
                 } else {
                     if (isUpdateSearch)
                         setDescription(UpdateUIMessages.InstallWizard_ReviewPage_zeroUpdates);
                     else
                         setDescription(UpdateUIMessages.InstallWizard_ReviewPage_zeroFeatures);
                     label.setText(""); //$NON-NLS-1$
 }
             }
         });
     }
     
     private void selectTrueUpdates() {
         ArrayList trueUpdates = new ArrayList ();
         for (int i=0; i<jobs.size(); i++) {
             IInstallFeatureOperation job = (IInstallFeatureOperation)jobs.get(i);
             if (!UpdateUtils.isPatch(job.getFeature()))
                 trueUpdates.add(job);
         }
         treeViewer.setCheckedElements(trueUpdates.toArray());
         validateSelection(new NullProgressMonitor());
     }

     /**
      * @see DialogPage#createControl(Composite)
      */
     public Control createContents(Composite parent) {
         Composite client = new Composite(parent, SWT.NULL);
         GridLayout layout = new GridLayout();
         layout.numColumns = 2;
         layout.marginWidth = layout.marginHeight = 0;
         client.setLayout(layout);
         label = new Label(client, SWT.NULL);
         label.setText(UpdateUIMessages.InstallWizard_ReviewPage_label);
         GridData gd = new GridData();
         gd.horizontalSpan = 2;
         label.setLayoutData(gd);

         createTreeViewer(client);

         Composite comp = new Composite(client, SWT.NONE);
         layout = new GridLayout();
         layout.marginWidth = layout.marginHeight = 0;
         comp.setLayout(layout);
         comp.setLayoutData(new GridData(GridData.FILL_VERTICAL));
                 
         Composite buttonContainer = new Composite(comp, SWT.NULL);
         gd = new GridData(GridData.FILL_VERTICAL);
         buttonContainer.setLayoutData(gd);
         layout = new GridLayout();
         layout.marginWidth = 0;
         layout.marginHeight = 0; //30?
 buttonContainer.setLayout(layout);
         buttonContainer.setLayoutData(new GridData(GridData.FILL_BOTH));

         Button button = new Button(buttonContainer, SWT.PUSH);
         button.setText(UpdateUIMessages.InstallWizard_ReviewPage_deselectAll);
         gd =
             new GridData(
                 GridData.HORIZONTAL_ALIGN_FILL
                     | GridData.VERTICAL_ALIGN_BEGINNING);
         button.setLayoutData(gd);
         SWTUtil.setButtonDimensionHint(button);
         button.addSelectionListener(new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 handleDeselectAll();
             }
         });

         moreInfoButton = new Button(buttonContainer, SWT.PUSH);
         moreInfoButton.setText(UpdateUIMessages.InstallWizard_ReviewPage_moreInfo);
         gd =
             new GridData(
                 GridData.HORIZONTAL_ALIGN_FILL
                     | GridData.VERTICAL_ALIGN_BEGINNING);
         moreInfoButton.setLayoutData(gd);
         SWTUtil.setButtonDimensionHint(moreInfoButton);
         moreInfoButton.addSelectionListener(new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 handleMoreInfo();
             }
         });
         moreInfoButton.setEnabled(false);
         
         
         propertiesButton = new Button(buttonContainer, SWT.PUSH);
         propertiesButton.setText(UpdateUIMessages.InstallWizard_ReviewPage_properties);
         gd =
             new GridData(
                 GridData.HORIZONTAL_ALIGN_FILL
                     | GridData.VERTICAL_ALIGN_BEGINNING);
         propertiesButton.setLayoutData(gd);
         SWTUtil.setButtonDimensionHint(propertiesButton);
         propertiesButton.addSelectionListener(new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 handleProperties();
             }
         });
         propertiesButton.setEnabled(false);

         selectRequiredFeaturesButton = new Button(buttonContainer, SWT.PUSH);
         selectRequiredFeaturesButton.setText(UpdateUIMessages.InstallWizard_ReviewPage_selectRequired);
         gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL
                 | GridData.VERTICAL_ALIGN_BEGINNING);
         selectRequiredFeaturesButton.setLayoutData(gd);
         SWTUtil.setButtonDimensionHint(selectRequiredFeaturesButton);
         selectRequiredFeaturesButton
                 .addSelectionListener(new SelectionAdapter() {
                     public void widgetSelected(SelectionEvent e) {
                         BusyIndicator.showWhile(e.display, new Runnable () {
                             public void run() {
                                 selectRequiredFeatures();
                                 updateItemCount();
                             }
                         });
                     }
                 });

         statusButton = new Button(buttonContainer, SWT.PUSH);
         statusButton.setText(UpdateUIMessages.InstallWizard_ReviewPage_showStatus);
         gd =
             new GridData(
                 GridData.HORIZONTAL_ALIGN_FILL
                     | GridData.VERTICAL_ALIGN_BEGINNING);
         statusButton.setLayoutData(gd);
         SWTUtil.setButtonDimensionHint(statusButton);
         statusButton.addSelectionListener(new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 showStatus();
             }
         });

         //new Label(client, SWT.NULL);

         counterLabel = new Label(client, SWT.NULL);
         gd = new GridData();
         gd.horizontalSpan = 2;
         counterLabel.setLayoutData(gd);

         filterOlderVersionCheck = new Button(client, SWT.CHECK);
         filterOlderVersionCheck.setText(UpdateUIMessages.InstallWizard_ReviewPage_filterOlderFeatures);
         filterOlderVersionCheck.setSelection(true);
         treeViewer.addFilter(olderVersionFilter);
         filterOlderVersionCheck.addSelectionListener(new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {

                 IInstallFeatureOperation[] jobs = getSelectedJobs();
                 
                 if (filterOlderVersionCheck.getSelection())
                     treeViewer.addFilter(olderVersionFilter);
                 else
                     treeViewer.removeFilter(olderVersionFilter);
                 
                 olderVersionFilter.setSelectedJobs(jobs);
                 pageChanged(jobs);
             }
         });
         gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
         gd.horizontalSpan = 2;
         filterOlderVersionCheck.setLayoutData(gd);
         
         filterCheck = new Button(client, SWT.CHECK);
         filterCheck.setText(UpdateUIMessages.InstallWizard_ReviewPage_filterFeatures);
         filterCheck.setSelection(false);
         //tableViewer.addFilter(filter);
 filterCheck.addSelectionListener(new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 
                 IInstallFeatureOperation[] jobs = getSelectedJobs();
                 
                 if (filterCheck.getSelection()) {
                     // make sure model is local
 if (downloadIncludedFeatures()) {
                         treeViewer.addFilter(filter);
                     } else {
                         filterCheck.setSelection(false);
                     }
                 } else {
                     treeViewer.removeFilter(filter);
                 }
                 
                 filter.setSelectedJobs(jobs);
                 
                 pageChanged(jobs);
             }
         });
         gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
         gd.horizontalSpan = 2;
         filterCheck.setLayoutData(gd);
         
         pageChanged();

         PlatformUI.getWorkbench().getHelpSystem().setHelp(client, "org.eclipse.update.ui.MultiReviewPage2"); //$NON-NLS-1$

         Dialog.applyDialogFont(parent);

         return client;
     }

     private void createTreeViewer(final Composite parent) {
         SashForm sform = new SashForm(parent, SWT.VERTICAL);
         GridData gd = new GridData(GridData.FILL_BOTH);
         gd.widthHint = 250;
         gd.heightHint =100;
         sform.setLayoutData(gd);
         
         treeViewer = new ContainerCheckedTreeViewer(sform, SWT.H_SCROLL | SWT.V_SCROLL
                 | SWT.BORDER);
         treeViewer.getTree().setLayoutData(new GridData(GridData.FILL_BOTH));
         treeViewer.setContentProvider(new TreeContentProvider());
         treeViewer.setLabelProvider(new TreeLabelProvider());
         treeViewer.setInput(UpdateUI.getDefault().getUpdateModel());

         treeViewer
             .addSelectionChangedListener(new ISelectionChangedListener() {
             public void selectionChanged(SelectionChangedEvent e) {
                 handleSelectionChanged((IStructuredSelection) e.getSelection());
             }
         });
         
         treeViewer.addCheckStateListener(new ICheckStateListener() {
             public void checkStateChanged(CheckStateChangedEvent event) {
                 /*
                  * validateSelection(); Object site =
                  * getSite(event.getElement()); ArrayList descendants = new
                  * ArrayList(); collectDescendants(site, descendants); Object[]
                  * nodes = new Object[descendants.size()]; for (int i = 0; i <
                  * nodes.length; i++) nodes[i] = descendants.get(i);
                  * treeViewer.update(nodes, null); updateItemCount();
                  */
                 try {
                     getContainer().run(true, true,
                             getCheckStateOperation(event, parent.getDisplay()));
                     getContainer().updateButtons();
                     updateStatusButton();
                 } catch (InvocationTargetException e) {
                     UpdateUI.logException(e);
                 } catch (InterruptedException e) {
                     UpdateUI.logException(e);
                 }
             }
         });
         
       descLabel = new ScrolledFormText(sform, true);
       descLabel.setText(""); //$NON-NLS-1$
 descLabel.setBackground(parent.getBackground());
       HyperlinkSettings settings = new HyperlinkSettings(parent.getDisplay());
       descLabel.getFormText().setHyperlinkSettings(settings);
       descLabel.getFormText().addHyperlinkListener(new HyperlinkAdapter() {
           public void linkActivated(HyperlinkEvent e) {
               Object href = e.getHref();
               if (href==null)
                   return;
               try {
                   URL url = new URL (href.toString());
                   PlatformUI.getWorkbench().getBrowserSupport().getExternalBrowser().openURL(url);
               }
               catch (PartInitException ex) {
                   UpdateUI.logException(ex);
               }
               catch (MalformedURLException ex) {
                   UpdateUI.logException(ex);
               }
           }
       });
       
       gd = new GridData(SWT.FILL, SWT.FILL, true, true);
       gd.horizontalSpan = 1;
       descLabel.setLayoutData(gd);
       
       sform.setWeights(new int[] {10, 2});
     }
     
     private IRunnableWithProgress getCheckStateOperation(
             final CheckStateChangedEvent event, final Display display) {
         return new IRunnableWithProgress() {
             public void run(IProgressMonitor monitor) {
                 monitor.beginTask(UpdateUIMessages.ReviewPage_validating,
                         IProgressMonitor.UNKNOWN);
                 validateSelection(monitor);
                 if (monitor.isCanceled()) {
                     undoStateChange(event);
                     monitor.done();
                     return;
                 }
                 Object site = getSite(event.getElement());
                 ArrayList descendants = new ArrayList ();
                 collectDescendants(site, descendants, monitor);
                 final Object [] nodes = new Object [descendants.size()];
                 if (monitor.isCanceled()) {
                     undoStateChange(event);
                     monitor.done();
                     return;
                 }
                 for (int i = 0; i < nodes.length; i++)
                     nodes[i] = descendants.get(i);
                 display.syncExec(new Runnable () {
                     public void run() {
                         treeViewer.update(nodes, null);
                         updateItemCount();
                     }
                 });
                 monitor.done();
             }
         };
     }
     
     private void undoStateChange(final CheckStateChangedEvent e) {
         treeViewer.getControl().getDisplay().syncExec(new Runnable () {
             public void run() {
                 treeViewer.setChecked(e.getElement(), !e.getChecked());
             }
         });
     }
     
     private void handleSelectionChanged(IStructuredSelection ssel) {
         
         Object item = ssel.getFirstElement();
         String description = null;
         if (item instanceof SiteBookmark) {
             description = ((SiteBookmark)item).getDescription();
         } else if (item instanceof SiteCategory) {
             IURLEntry descEntry = ((SiteCategory)item).getCategory().getDescription();
             if (descEntry != null)
                 description = descEntry.getAnnotation();
         } else if (item instanceof IInstallFeatureOperation) {
             jobSelected(ssel);
             return;
         }

         if (description == null)
             description = ""; //$NON-NLS-1$
 //descLabel.setText(UpdateManagerUtils.getWritableXMLString(description), false, true);
 updateDescription(description);
         propertiesButton.setEnabled(false);
         moreInfoButton.setEnabled(false);
     }

 /*
     private void fillContextMenu(IMenuManager manager) {
         if (treeViewer.getSelection().isEmpty()) return;
         Action action = new Action(UpdateUIMessages.InstallWizard_ReviewPage_prop) {
             public void run() {
                 handleProperties();
             }
         };
         manager.add(action);
     }
 */

     private void jobSelected(IStructuredSelection selection) {
         IInstallFeatureOperation job = (IInstallFeatureOperation) selection.getFirstElement();
         IFeature feature = job != null ? job.getFeature() : null;
         IURLEntry descEntry = feature != null ? feature.getDescription() : null;
         String desc = null;
         if (descEntry != null)
             desc = descEntry.getAnnotation();
         if (desc == null)
             desc = ""; //$NON-NLS-1$
 //descLabel.setText(UpdateManagerUtils.getWritableXMLString(desc));
 updateDescription(desc);
         propertiesButton.setEnabled(feature != null);
         moreInfoButton.setEnabled(job != null && getMoreInfoURL(job) != null);
     }
     
     private void updateDescription(String text) {
         descLabel.getFormText().setText(UpdateManagerUtils.getWritableXMLString(text), false, true);
         descLabel.reflow(true);
     }
     
     private void pageChanged() {
         pageChanged(this.getSelectedJobs());
     }
     
     private void pageChanged( IInstallFeatureOperation[] jobsSelected) {
     
         if (jobsSelected.length == 0) {
             lastDisplayedStatus = null;
             setErrorMessage(null);
             setPageComplete(false);
             setValidationStatus(null);
             problematicFeatures.clear();
         }
         treeViewer.setCheckedElements(jobsSelected);
         //validateSelection();
 treeViewer.refresh();
         treeViewer.setCheckedElements(jobsSelected);
         updateItemCount();
     }
     
     private void setValidationStatus(IStatus newValidationStatus) {
         this.validationStatus = newValidationStatus;
         updateStatusButton();
     }
     
     private void updateStatusButton() {
         statusButton.getDisplay().syncExec(new Runnable () {
             public void run() {
                 boolean newState = validationStatus != null && validationStatus.getSeverity() != IStatus.OK;
                 statusButton.setEnabled(newState);
             }
         });
     }
     
     private void updateItemCount() {
         updateItemCount(-1, -1);
     }
     
     private int getSelectedJobsUniqueCount() {
         Object [] checkedElements = getSelectedJobs();
         Set set = new HashSet ();
         for (int i=0; i<checkedElements.length; i++) {
             IInstallFeatureOperation job = (IInstallFeatureOperation)checkedElements[i];
             IFeature feature = job.getFeature();
             if (set.contains(feature))
                 continue;
             set.add(feature);
         }
         return set.size();
     }

     private void updateItemCount(int checkedCount, int totalCount) {
         if (checkedCount == -1) {
             checkedCount = getSelectedJobsUniqueCount();
         }
         if (totalCount == -1) {
             totalCount = jobs.size();
         }
         String total = "" + totalCount; //$NON-NLS-1$
 String selected = "" + checkedCount; //$NON-NLS-1$
 counterLabel.setText(
             NLS.bind(UpdateUIMessages.InstallWizard_ReviewPage_counter, (new String [] { selected, total })));
         counterLabel.getParent().layout();
     }

 // private void handleSelectAll(boolean select) {
 // treeViewer.setAllChecked(select);
 //// make sure model is local (download using progress monitor from container)
 // downloadIncludedFeatures();
 //
 // treeViewer.getControl().getDisplay().asyncExec(new Runnable() {
 // public void run() {
 // pageChanged();
 // }
 // });
// }

// private void handleSelectAll(boolean select) {
// treeViewer.setAllChecked(select);
//// make sure model is local (download using progress monitor from container)
// downloadIncludedFeatures();
//
// treeViewer.getControl().getDisplay().asyncExec(new Runnable() {
// public void run() {
// pageChanged();
// }
// });
//}

   private void handleDeselectAll() {
        //treeViewer.setCheckedElements(new Object[0]);
 IInstallFeatureOperation[] selectedJobs = getSelectedJobs();
         for( int i = 0; i < selectedJobs.length; i++)
             treeViewer.setChecked( selectedJobs[i], false);
        // make sure model is local (download using progress monitor from
 // container)
// downloadIncludedFeatures();

        treeViewer.getControl().getDisplay().asyncExec(new Runnable () {
            public void run() {
                pageChanged();
            }
        });
}
    
    private void handleProperties() {
        final IStructuredSelection selection =
            (IStructuredSelection) treeViewer.getSelection();

        final IInstallFeatureOperation job =
            (IInstallFeatureOperation) selection.getFirstElement();
        if (propertiesAction == null) {
            propertiesAction =
                new FeaturePropertyDialogAction(getShell(), treeViewer);
        }

        BusyIndicator
            .showWhile(treeViewer.getControl().getDisplay(), new Runnable () {
            public void run() {
                SimpleFeatureAdapter adapter =
                    new SimpleFeatureAdapter(job.getFeature());
                propertiesAction.selectionChanged(
                    new StructuredSelection(adapter));
                propertiesAction.run();
            }
        });
    }

    private String getMoreInfoURL(IInstallFeatureOperation job) {
        IURLEntry desc = job.getFeature().getDescription();
        if (desc != null) {
            URL url = desc.getURL();
            return (url == null) ? null : url.toString();
        }
        return null;
    }

    private void handleMoreInfo() {
        IStructuredSelection selection =
            (IStructuredSelection) treeViewer.getSelection();
        final IInstallFeatureOperation selectedJob =
            (IInstallFeatureOperation) selection.getFirstElement();
        BusyIndicator
            .showWhile(treeViewer.getControl().getDisplay(), new Runnable () {
            public void run() {
                String urlName = getMoreInfoURL(selectedJob);
                UpdateUI.showURL(urlName);
            }
        });
    }

    private IStatus selectRequiredFeatures() {
        
        IInstallFeatureOperation[] jobs = getSelectedJobs();
        RequiredFeaturesResult requiredFeaturesResult = ((OperationValidator)OperationsManager
                .getValidator()).getRequiredFeatures(jobs);
        setValidationStatus(requiredFeaturesResult.getStatus());
        Set requiredFeatures = requiredFeaturesResult.getRequiredFeatures();
        problematicFeatures.clear();
        
        Iterator requiredFeaturesIterator = requiredFeatures.iterator();
        ArrayList toBeInstalled = new ArrayList ();
        
        while (requiredFeaturesIterator.hasNext()) {
            IImport requiredFeature = ((InternalImport)requiredFeaturesIterator.next()).getImport();

            IInstallFeatureOperation currentFeatureSelected = null;
            TreeItem[] items = treeViewer.getTree().getItems();
            for (int i = 0; i < items.length; i++) {
                TreeItem[] siteRootContent = items[i].getItems();
                for (int j = 0; j < siteRootContent.length; j++) {
                    if (siteRootContent[j].getData() instanceof SiteCategory) {
                        
                        if ( !treeViewer.getChecked(siteRootContent[j].getData())) {
                            // this category has not been checked at all so we have to create its features
 treeViewer.createChildren(siteRootContent[j]);
                        }
                        TreeItem[] features = siteRootContent[j].getItems();
                        if ((features.length > 0) && (features[0].getData() == null)) {
                            // this category has been checked but not visited yet so restore the features in it
 treeViewer.createChildren(siteRootContent[j]);
                            treeViewer.updateChildrenItems(siteRootContent[j]);
                            features = siteRootContent[j].getItems();
                        }
                        
                        for (int k = 0; k < features.length; k++) {
                            currentFeatureSelected = decideOnFeatureSelection(
                                    requiredFeature,
                                    (IInstallFeatureOperation) features[k]
                                            .getData(), currentFeatureSelected);
                        }
                    } else if (siteRootContent[j].getData() instanceof IInstallFeatureOperation) {
                        currentFeatureSelected = decideOnFeatureSelection(
                                requiredFeature,
                                (IInstallFeatureOperation) siteRootContent[j]
                                        .getData(), currentFeatureSelected);
                    }
                }
            }

            if (currentFeatureSelected != null)
                toBeInstalled.add(currentFeatureSelected);
        }

        if (!toBeInstalled.isEmpty()) {
            Iterator toBeInstalledIterator = toBeInstalled.iterator();
            while (toBeInstalledIterator.hasNext()) {
                IInstallFeatureOperation current = (IInstallFeatureOperation)toBeInstalledIterator.next();
                treeViewer.setChecked(current, true);
            }
            return selectRequiredFeatures();
        } else {
            problematicFeatures.clear();
            if (validationStatus != null) {
                IStatus[] status = validationStatus.getChildren();
                for (int i = 0; i < status.length; i++) {
                    IStatus singleStatus = status[i];
                    if (isSpecificStatus(singleStatus)) {
                        IFeature f = ((FeatureStatus) singleStatus)
                                .getFeature();
                        problematicFeatures.add(f);
                    }
                }
            }

            setPageComplete(validationStatus == null
                    || validationStatus.getSeverity() == IStatus.WARNING);

            lastDisplayedStatus = null;
            updateWizardMessage();
            
            treeViewer.update(getSelectedJobs(), null);
            return validationStatus;
        }
    }

    public IInstallFeatureOperation[] getSelectedJobs() {
        Object [] selected = treeViewer.getCheckedElements();
        ArrayList selectedJobs = new ArrayList (selected.length);
        for (int i=0; i<selected.length; i++)
            if (selected[i] instanceof IInstallFeatureOperation)
                selectedJobs.add(selected[i]);
        return (IInstallFeatureOperation[])selectedJobs.toArray(new IInstallFeatureOperation[selectedJobs.size()]);
    }
    
    public void validateSelection(IProgressMonitor monitor) {
        IInstallFeatureOperation[] jobs;

        final IInstallFeatureOperation[][] bag = new IInstallFeatureOperation[1][];
        treeViewer.getControl().getDisplay().syncExec(new Runnable () {
            public void run() {
                bag[0] = getSelectedJobs();
            }
        });
        if (monitor.isCanceled()) return;
        jobs = bag[0];
        setValidationStatus(OperationsManager.getValidator()
                .validatePendingChanges(jobs));
        problematicFeatures.clear();
        if (monitor.isCanceled()) return;
        if (validationStatus != null) {
            IStatus[] status = validationStatus.getChildren();
            for (int i = 0; i < status.length; i++) {
                IStatus singleStatus = status[i];
                if (isSpecificStatus(singleStatus)) {
                    IFeature f = ((FeatureStatus) singleStatus).getFeature();
                    problematicFeatures.add(f);
                }
            }
        }
        if (monitor.isCanceled())
                return;
        treeViewer.getControl().getDisplay().syncExec(new Runnable () {
            public void run() {
                setPageComplete(validationStatus == null
                        || validationStatus.getSeverity() == IStatus.WARNING);
/*
                statusButton.setEnabled(validationStatus != null
                        && validationStatus.getSeverity() != IStatus.OK);
                        */

                updateWizardMessage();
            }
        });
    }

    private void showStatus() {
        if (validationStatus != null) {
            new StatusDialog().open();
        }

    }
    /**
     * Check whether status is relevant to show for
     * a specific feature or is a other problem
     * @param status
     * @return true if status is FeatureStatus with
     * specified feature and certain error codes
     */
    private boolean isSpecificStatus(IStatus status){
        if(!(status instanceof FeatureStatus)){
            return false;
        }
        if(status.getSeverity()!=IStatus.ERROR){
            return false;
        }
        FeatureStatus featureStatus = (FeatureStatus) status;
        if(featureStatus.getFeature()==null){
            return false;
        }
        return 0!= (featureStatus.getCode()
                & FeatureStatus.CODE_CYCLE
                + FeatureStatus.CODE_ENVIRONMENT
                + FeatureStatus.CODE_EXCLUSIVE
                + FeatureStatus.CODE_OPTIONAL_CHILD
                + FeatureStatus.CODE_PREREQ_FEATURE
                + FeatureStatus.CODE_PREREQ_PLUGIN);
    }
    /**
     * Update status in the wizard status area
     */
    private void updateWizardMessage() {
        
        if (validationStatus == null) {
            lastDisplayedStatus=null;
            setErrorMessage(null);
        } else if (validationStatus.getSeverity() == IStatus.WARNING) {
            lastDisplayedStatus=null;
            setErrorMessage(null);
            setMessage(validationStatus.getMessage(), IMessageProvider.WARNING);
        } else {
            // 1. Feature selected, creating a problem for it, show status for it
 if(newlySelectedFeature !=null){
                IStatus[] status = validationStatus.getChildren();
                for(int s =0; s< status.length; s++){
                    if(isSpecificStatus(status[s])){
                        FeatureStatus featureStatus = (FeatureStatus)status[s];
                        if(newlySelectedFeature.equals(featureStatus.getFeature())){
                            lastDisplayedStatus=featureStatus;
                            setErrorMessage(featureStatus.getMessage());
                            return;
                        }
                    }
                }
            }
            
            // 2. show old status if possible (it is still valid)
 if(lastDisplayedStatus !=null){
                IStatus[] status = validationStatus.getChildren();
                for(int i=0; i<status.length; i++){
                    if(lastDisplayedStatus.equals(status[i])){
                        //lastDisplayedStatus=lastDisplayedStatus;
 //setErrorMessage(status[i].getMessage());
 return;
                    }
                }
                lastDisplayedStatus = null;
            }
            
            // 3. pick the first problem that is specific to some feature
 IStatus[] status = validationStatus.getChildren();
            for(int s =0; s< status.length; s++){
                if(isSpecificStatus(status[s])){
                    lastDisplayedStatus = (FeatureStatus)status[s];
                    setErrorMessage(status[s].getMessage());
                    return;
                }
            }
                
            // 4. display the first problem (no problems specify a feature)
 if(status.length>0){
                IStatus singleStatus=status[0];
                setErrorMessage(singleStatus.getMessage());
            }else{
            // 5. not multi or empty multi status
 setErrorMessage(UpdateUIMessages.InstallWizard_ReviewPage_invalid_long);
            }
        }
    }

    class StatusDialog extends ErrorDialog {
// Button detailsButton;
 public StatusDialog() {
            super(getContainer().getShell(), UpdateUIMessages.InstallWizard_ReviewPage_invalid_short, null,
                    validationStatus, IStatus.OK | IStatus.INFO
                            | IStatus.WARNING | IStatus.ERROR);
        }
// protected Button createButton(
// Composite parent,
// int id,
// String label,
// boolean defaultButton) {
// Button b = super.createButton(parent, id, label, defaultButton);
// if(IDialogConstants.DETAILS_ID == id){
// detailsButton = b;
// }
// return b;
// }
 public void create() {
            super.create();
            buttonPressed(IDialogConstants.DETAILS_ID);
// if(detailsButton!=null){
// detailsButton.dispose();
// }
 }
    }

    /**
     * @return true, if completed, false if canceled by the user
     */
    private boolean downloadIncludedFeatures() {
        try {
            Downloader downloader = new Downloader(jobs);
            getContainer().run(true, true, downloader);
            return !downloader.isCanceled();
        } catch (InvocationTargetException ite) {
        } catch (InterruptedException ie) {
        }
        return true;
    }
    /**
     * Runnable to resolve included feature references.
     */
    class Downloader implements IRunnableWithProgress {
        boolean canceled = false;
        /**
         * List of IInstallFeatureOperation
         */
        ArrayList operations;
        public Downloader(ArrayList installOperations) {
            operations = installOperations;
        }
        public boolean isCanceled() {
            return canceled;
        }
        public void run(IProgressMonitor monitor)
                throws InvocationTargetException , InterruptedException {
            for (int i = 0; i < operations.size(); i++) {
                IInstallFeatureOperation candidate = (IInstallFeatureOperation) operations
                        .get(i);
                IFeature feature = candidate.getFeature();
                try {
                    IFeatureReference[] irefs = feature
                            .getRawIncludedFeatureReferences();
                    for (int f = 0; f < irefs.length; f++) {
                        if (monitor.isCanceled()) {
                            canceled = true;
                            return;
                        }
                        IFeatureReference iref = irefs[f];
                        iref.getFeature(monitor);
                    }
                } catch (CoreException e) {
                }
            }
            if (monitor.isCanceled()) {
                canceled = true;
            }
        }
    }
    
    private IInstallFeatureOperation findJob(FeatureReferenceAdapter feature)
            throws CoreException {
        if (jobs == null)
            return null;
        for (int i = 0; i < jobs.size(); i++)
            if (((IInstallFeatureOperation) jobs.get(i)).getFeature()
                    .getVersionedIdentifier().equals(feature.getFeatureReference()
                    .getVersionedIdentifier()))
                return (IInstallFeatureOperation) jobs.get(i);

        return null;
    }
    
    private Object getSite(Object object) {
        ITreeContentProvider provider = (ITreeContentProvider)treeViewer.getContentProvider();
        while (object != null && !(object instanceof SiteBookmark)) {
            object = provider.getParent(object);
        }
        return object;
    }
    
    private void collectDescendants(Object root, ArrayList list,
            IProgressMonitor monitor) {
        ITreeContentProvider provider = (ITreeContentProvider) treeViewer
                .getContentProvider();
        Object [] children = provider.getChildren(root);
        if (children != null && children.length > 0)
            for (int i = 0; i < children.length; i++) {
                if (monitor.isCanceled())
                    return;
                list.add(children[i]);
                collectDescendants(children[i], list, monitor);
            }
    }

    
    public boolean isFeatureGood(IImport requiredFeature, IFeature feature) {
        return isFeatureGood(requiredFeature, feature, new ArrayList ());
    }
    
    public boolean isFeatureGood(IImport prereq, IFeature feature, List visitedFeatures) {

        if (prereq.getKind() == IImport.KIND_FEATURE) {
            if ((!prereq.getVersionedIdentifier().getIdentifier().equals(
                    feature.getVersionedIdentifier().getIdentifier()))) {
                IIncludedFeatureReference[] iifr = null;
                try {
                    iifr = feature.getIncludedFeatureReferences();
                } catch (CoreException e) {
                    UpdateUI.logException(e);
                    // if we can not get included features then they can not satisfy requirement, so just ignore them
 return false;
                }
                if (iifr == null) {
                    return false;
                }
                
                for(int i = 0; i < iifr.length; i++) {
                    IFeature current;
                    try {
                        current = UpdateUtils.getIncludedFeature(feature, iifr[i]);
                    } catch (CoreException e) {
                        // if we can not get feature then it can not satisfy requirement, so just ignore it
 UpdateUI.logException(e);
                        continue;
                    }
                    if (!visitedFeatures.contains(current)) {
                        visitedFeatures.add(current);
                        if (isFeatureGood(prereq, current, visitedFeatures)) {
                            return true;
                        }
                    }
                }
                
                return false;
            }

            int rule = (prereq.getRule() != IImport.RULE_NONE) ? prereq.getRule() : IImport.RULE_COMPATIBLE;

            switch (rule) {
            case IImport.RULE_PERFECT: return feature.getVersionedIdentifier().getVersion().isPerfect(
                                prereq.getVersionedIdentifier()
                                .getVersion());
            case IImport.RULE_EQUIVALENT:
                        return feature.getVersionedIdentifier().getVersion()
                        .isEquivalentTo(
                                prereq.getVersionedIdentifier()
                                .getVersion());
            case IImport.RULE_COMPATIBLE:
                return feature.getVersionedIdentifier().getVersion()
                        .isCompatibleWith(
                                prereq.getVersionedIdentifier()
                                .getVersion());
            case IImport.RULE_GREATER_OR_EQUAL:
                        return feature.getVersionedIdentifier().getVersion()
                        .isGreaterOrEqualTo(
                                prereq.getVersionedIdentifier()
                                .getVersion());
            }

            return false;
        } else {
            if ((prereq.getKind() == IImport.KIND_PLUGIN)) {
                return checkIfFeatureHasPlugin( prereq, feature);
            }
            return false;
        }
    }
    
    private boolean checkIfFeatureHasPlugin(IImport requiredFeature, IFeature feature) {
        
        IPluginEntry[] plugins = feature.getPluginEntries();
        try {
            List includedPlugins = getPluginEntriesFromIncludedFeatures(feature, new ArrayList (), new ArrayList ());
            includedPlugins.addAll(Arrays.asList(plugins));
            plugins = (IPluginEntry[])includedPlugins.toArray( new IPluginEntry[includedPlugins.size()]);
        } catch( CoreException ce) {
            UpdateUI.logException(ce);
            // ignore this plugins can not sutisfy requirement anyways
 }
        if (plugins == null) {
            return false;
        }
        
        for(int i = 0; i < plugins.length; i++) {
            if (isMatch(plugins[i].getVersionedIdentifier(), requiredFeature.getVersionedIdentifier(), requiredFeature.getIdRule())) {
                return true;
            }
        }
        
        return false;
    }

    private List getPluginEntriesFromIncludedFeatures(IFeature feature, List plugins, List visitedFeatures) throws CoreException {
        IIncludedFeatureReference[] iifr = feature.getIncludedFeatureReferences();
        for(int i = 0; i < iifr.length; i++) {
            IFeature current = UpdateUtils.getIncludedFeature( feature, iifr[i]);
            if (!visitedFeatures.contains(current)) {
                IPluginEntry[] pluginEntries = current.getPluginEntries();
                plugins.addAll(Arrays.asList(pluginEntries));
                visitedFeatures.add(current);
                getPluginEntriesFromIncludedFeatures(current, plugins, visitedFeatures);
            }
        }
        
        return plugins;
    }

    // vid1 = feature
 // vid2 = requiredFeature
 private boolean isMatch( VersionedIdentifier vid1, VersionedIdentifier vid2, int rule) {
        
        if (!vid1.getIdentifier().equals(vid2.getIdentifier())) {
            return false;
        }
        if ( vid2.getVersion().getMajorComponent() == 0 && vid2.getVersion().getMinorComponent() == 0 && vid2.getVersion().getServiceComponent() == 0 ) {
            //version is ignored
 return true;
        }
        switch (rule) {
        case IImport.RULE_PERFECT:
            return vid1.getVersion().isPerfect(vid2.getVersion());
        case IImport.RULE_EQUIVALENT:
            return vid1.getVersion().isEquivalentTo(vid2.getVersion());
        case IImport.RULE_COMPATIBLE:
            return vid1.getVersion().isCompatibleWith(vid2.getVersion());
        case IImport.RULE_GREATER_OR_EQUAL:
            return vid1.getVersion().isGreaterOrEqualTo(vid2.getVersion());
        }
        return false;
    }

    public boolean isFeatureBetter(IInstallFeatureOperation feature,
            IInstallFeatureOperation currentFeatureSelected) {

        if (currentFeatureSelected == null)
            return true;
        // If the feature is the same, pick the newer one
 if (currentFeatureSelected.getFeature().getVersionedIdentifier().getIdentifier().equals(
                feature.getFeature().getVersionedIdentifier().getIdentifier())) {
            return !currentFeatureSelected.getFeature().getVersionedIdentifier()
            .getVersion().isGreaterOrEqualTo(
                    feature.getFeature().getVersionedIdentifier()
                            .getVersion());
        }
        else {
            // Different features.
 // Pick a feature with smaller number of plug-ins
 NullProgressMonitor monitor = new NullProgressMonitor();
            int currentNumber = getTotalNumberOfPluginEntries(currentFeatureSelected.getFeature(), monitor);
            int newNumber = getTotalNumberOfPluginEntries(feature.getFeature(), monitor);
            return newNumber<currentNumber;
        }
    }
    
    private int getTotalNumberOfPluginEntries(IFeature feature, IProgressMonitor monitor) {
        int count = 0;
        try {
            count = feature.getPluginEntryCount();
            IIncludedFeatureReference [] irefs = feature.getIncludedFeatureReferences();
            for (int i=0; i<irefs.length; i++) {
                IFeature child = irefs[i].getFeature(monitor);
                count += getTotalNumberOfPluginEntries(child, monitor);
            }
        }
        catch (CoreException e) {
        }
        return count;
    }

    public IInstallFeatureOperation decideOnFeatureSelection(
            IImport requiredFeature, IInstallFeatureOperation feature,
            IInstallFeatureOperation currentFeatureSelected) {

        if (isFeatureGood(requiredFeature, feature.getFeature()) && isFeatureBetter(feature, currentFeatureSelected)) {
                return feature;
        } else {
            return currentFeatureSelected;
        }
    }
    
    private boolean isFeatureProblematic(IFeature feature) {
        
        if ( problematicFeatures.contains(feature) )
            return true;
        
        IImport[] iimports = feature.getImports();
        
        for(int i = 0; i < iimports.length; i++) {
            Iterator problematicFeatures = this.problematicFeatures.iterator();
            while(problematicFeatures.hasNext()) {
                if (iimports[i].getVersionedIdentifier().equals( ((IFeature)problematicFeatures.next()).getVersionedIdentifier()) ) {
                    return true;
                }
            }
        }
        try {
            Iterator includedFeatures = OperationValidator.computeFeatureSubtree(feature, null, null, false, new ArrayList (), null ).iterator();
            while (includedFeatures.hasNext()) {
                Iterator problematicFeatures = this.problematicFeatures.iterator();
                VersionedIdentifier currentIncludedFeaturesVI = ((IFeature)includedFeatures.next()).getVersionedIdentifier();
                while (problematicFeatures.hasNext()) {
                    Object currentProblematicFeatures = problematicFeatures.next();
                    if (currentProblematicFeatures instanceof IFeature) {
                        VersionedIdentifier currentProblematicFeaturesVI = ((IFeature)currentProblematicFeatures).getVersionedIdentifier();
                        if (currentProblematicFeaturesVI.equals( currentIncludedFeaturesVI) ) {
                            return true;
                        }
                    }
                }
            }
        } catch (CoreException ce) {
        }
        return false;
    }
    
    private boolean isSelected( IInstallFeatureOperation[] selectedJobs, IInstallFeatureOperation iInstallFeatureOperation) {
        
        if (selectedJobs == null)
            return false;
        
        for( int i = 0; i < selectedJobs.length; i++) {
            
            if (iInstallFeatureOperation.getFeature().getVersionedIdentifier().equals(selectedJobs[i].getFeature().getVersionedIdentifier()) &&
                iInstallFeatureOperation.getFeature().getSite().getURL().equals(selectedJobs[i].getFeature().getSite().getURL())) {
                return true;
            }
        }
        
        return false;
    }
}

